package mya_dc.slave;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.ClosedByInterruptException;
import java.util.LinkedList;

import javax.swing.JOptionPane;

import mya_dc.shared_classes.Message;
import mya_dc.shared_classes.SharedDefinitions;
import mya_dc.shared_classes.Transmitter;
import mya_dc.shared_classes.files_manipulation.FileObject;

/**
 * Main Class for Slave program.
 * <br>Slave accepts c & c++ files from compilation servers, compiles and sends back object files.
 *
 * <p>
 * <b>Notes:</b>
 * <ul>
 * <li>Class assumes existence of g++ at {root}\MinGW\\bin\\g++.exe
 * <li>If file don't exists error message will pop and program will exit.
 * </ul>
 * <p>
 * 
 * @author      Yuval Shimron
 * <br>			MYA
 */
public class Slave
{
	// shared network variables
	ServerSocket listenSocket = null;
	Socket mainSkt = null;
	int	masterServerPort;
	int slaveListenPort;
	boolean bAborted;
	String masterServerIP;
	Transmitter m_listenToMainServerTransmitter;
	Transmitter m_connectToMainServerTransmitter;
	
	public final static String packsDir = System.getProperty("user.dir")+"\\slaves\\slave" +
	+ new java.util.Date().getTime() + "\\packages\\";
	public final static String gcc = System.getProperty("user.dir")+ "\\MinGW\\bin\\g++.exe ";
	
	// SlaveThread compiles packages to compilation severs and ListnerThread manages this connection
	// All threads are kept in a list for monitoring
	LinkedList<SlaveThread> ThreadsList = new LinkedList<SlaveThread>();
	
	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		new Slave();
	}

	/**
	 * Constructor - Creates main GUI
	 *
	 */	
	public Slave()
	{
		try {	
			SlaveGUI window = new SlaveGUI(this, Thread.currentThread());
			window.setVisible(true);
			
			if (! new File(gcc).isFile())
			{
				JOptionPane.showMessageDialog(window.m_SlaveGUI, "File not found:\n" + gcc + "\nMake sure file exists before running MYA-DC Slave", "Error", 0);
				System.exit(0);
			}
			
			// creates main folders. 	
			new File(packsDir).mkdirs();		
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Creates listen socket
	 */
	protected boolean listen(int iListenPort)
	{
		try
		{
			listenSocket = new ServerSocket(iListenPort);
		}
		catch (Exception ioe)
		{
			return false;
		}
		return true;
	}
	
	/**
	 * Terminates the slave and exits
	 */
	protected void terminate()
	{
		bAborted = true;
		try {
			FileObject.deleteDirectory(new File(packsDir));
			ThreadsList.clear();
		} catch (Exception e) {}
		try {
			m_listenToMainServerTransmitter.close();
		} catch (Exception e) {}
		try {
			m_connectToMainServerTransmitter.close();
		} catch (Exception e) {}
	}
	
	/**
	 * Connects to Master Server
	 * 
	 * @param strHost - Master server I.P. number
	 * @param iHostPort - Master server port number
	 * @param iListenPort - Own listen port
	 */
	protected boolean connectToServer()
	{
		// Connect to Main Server and send it listening port
		try {
			bAborted = false;
			mainSkt = new Socket(masterServerIP, masterServerPort);
			m_connectToMainServerTransmitter = new Transmitter(mainSkt,null,
					SharedDefinitions.SecsBetweenTwoKeepAliveMessages);
			m_connectToMainServerTransmitter.send(
					new Message(
							Message.EMessageType.LOGIN_ENTITY_IDENTIFICATION, 
							SharedDefinitions.Entity.ENTITY_SLAVE		
						)
				);
			m_connectToMainServerTransmitter.send(
					new Message(Message.EMessageType.LISTEN_PORT, slaveListenPort)
				);
		}
		catch (ClosedByInterruptException e) {}
		catch (Exception e)
		{
			//try { listenSocket.close(); } catch (Exception eee) {}
			return false;
		}
		return true;
	}
	
	/**
	 * Listens and accepts connection
	 * 
	 * @param iCounter - current package number
	 * 
	 * @throws IOException - on network failure on accept()
	 */
	protected void listenAndAccept(int iCounter) throws IOException
	{
		Socket skt = null;
		// Wait for a connection from a Compilation Server.
		skt = listenSocket.accept();
		
		try {
			Transmitter trans = new Transmitter(skt, null, SharedDefinitions.SecsBetweenTwoKeepAliveMessages);
			if (!SharedDefinitions.isExpectedEntity(trans,SharedDefinitions.Entity.ENTITY_COMPILATION,true))
			{
				return;
			}
		}
		catch (Exception e) {
			return;
		}

		new File(packsDir + "Pack" + iCounter +"\\").mkdirs();
		new File(packsDir + "Pack" + iCounter +"-res\\").mkdirs();
		// creates handling thread to handle package
		SlaveThread sThread = new SlaveThread(packsDir + "Pack" + iCounter, skt, this);
		ThreadsList.add(sThread);
		sThread.start();
		iCounter++;
	}
	
	/**
	 * Stops listening
	 * 
	 */
	protected void stopListening()
	{
		try {
			listenSocket.close();
		}
		catch (Exception ee) { /* ignored */ }
	}
	
	/**
	 * Checks connectivity with Master Server
	 * 
	 * <p>
	 * <b>Notes:</b>
 	 * <ul>
 	 * <li>On disconnect tries to reconnect for 3 times.
 	 * <li>On connection loss function will end when all jobs are over to allow completeness of the compilation process 
 	 * </ul>
 	 * <p>
	 * 
	 */
	protected void listenToMainServer()
	{
		try {
			m_listenToMainServerTransmitter = new Transmitter(mainSkt, null,
					SharedDefinitions.SecsBetweenTwoKeepAliveMessages);
			m_listenToMainServerTransmitter.receive();
		}
		catch(Exception e) 	{
			int i = 0;
			boolean connected = false;
			
			if (! bAborted )
				while ( !(connected = connectToServer()) && i<3 )
				{
					try { Thread.sleep(2000); } catch(Exception ee) {}
					i++;
				}

			if (connected)
			{
				// reconnection succeeded.
				listenToMainServer();
			}
			else
			{
				// Main Server has been disconnected or terminate the connection
				// Slave will be closed when all jobs are done.
				stopListening();
				while(!ThreadsList.isEmpty())
				{
					try {
						Thread.sleep(1000);
					}
					catch (InterruptedException ie) {
						return;
					}
				}
			}
		}
		return;
	}
	
	/**
	 * Checks if jobs are still in progress
	 * 
	 */
	protected boolean runningThreads()
	{
		return !ThreadsList.isEmpty();
	}
	
	/**
	 * Kills running compilation threads using interrupt() method
	 * 
	 */
	protected void destroyThreads()
	{
		if (runningThreads())
		{
			for(SlaveThread sthread : ThreadsList)
			{
				if (!sthread.isAlive())
					ThreadsList.remove(sthread);
				// signal threads to abort and release them if they're waiting in accept() or something like that
				else {
					sthread.abort = true;
					sthread.interrupt();
				}
			}
		}
	}

	/**
	 * Removes a thread from the threads list
	 * 
	 */
	public synchronized void removeThread(SlaveThread slaveThread)
	{
		ThreadsList.remove(slaveThread);
	}
}
